#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
#include <CUnit/Automated.h>
#include <CUnit/Console.h>
#include <sys/socket.h>
#include <netinet/in.h> 
#include <arpa/inet.h>  
#include <stdlib.h>

#include "zip_conf.h"
#include "zip_radix_tree.h"
#include "zip_mem_pool.h"
#include "zip_location.h"

#define MESSAGE_HEAD_LENGTH 12
#define RENDER_SKIP_LENGTH 65530

typedef char (*p_twenty_array)[20];
void traver_func(void *node)
{
   if (node) 
   {
     location_t *loc = (location_t *)node;
     printf("location is %u", location_get_value(loc));
   }
}

static radix_tree_t *
test_trie_create()
{
   radix_tree_t *tree;
   CU_ASSERT_TRUE( NULL != (tree = radix_tree_create(NULL)) ); 
   return tree; 
}

static void
test_trie_destroy(radix_tree_t *tree)
{
   radix_tree_delete(tree); 
}


static void
random_insert(radix_tree_t *tree, p_twenty_array have_ip, unsigned int ip_num)
{
    char data[20];
    char ttmmpp[16];
    srand(time(NULL));
    unsigned int ran,n;
    unsigned  int i = 0;

    for (; i < ip_num; i++)
    {
		n = rand() % 15 + 16;
        ran = (rand() * rand()) % 4294967296;
		ran = htonl(ran);
		const	char *ret = inet_ntop(AF_INET, &ran, data, 16);
		if (NULL == ret)
			break;			
		strcpy(ttmmpp, data);
		strcat(data, "/");
		
		char num[3];
		sprintf(num, "%u", n);
		strcat(data, num);
        location_t *locat = (location_t*)malloc(sizeof(location_t));
	    locat->geo.country = 1;
	    locat->geo.isp = 1;
	    locat->geo.province = 1;
	    locat->geo.region = 1;
	    locat->geo.city = 1;
        int ret_int = radix_tree_insert_subnet(tree, data, locat);
        if (0 != ret_int)
        {
		    have_ip[i][0] = '#';
            free(locat);
            continue;
        }
		strcpy(have_ip[i], ttmmpp);
	   	CU_ASSERT_TRUE(ret_int == 0);
        
    }
}

void random_search(radix_tree_t *tree, p_twenty_array have_ip, unsigned int ip_num)
{
    unsigned  int i = 0;
	location_t locat;
	unsigned int count = 0;
    for(; i < ip_num; i++)
    {
        if (have_ip[i][0] != '#')
		    CU_ASSERT_TRUE( NULL != radix_tree_find_str(tree, have_ip[i]));
    }
}

static void
test_trie_insert(radix_tree_t *tree)
{
    location_t *locat = (location_t*)malloc(sizeof(location_t));
	locat->geo.country = 1;
	locat->geo.isp = 2;
	locat->geo.province = 3;
	locat->geo.region = 4;
	locat->geo.city = 5;
    CU_ASSERT_TRUE( 0 == radix_tree_insert_subnet(tree, "2.2.2.0/24", locat));
    CU_ASSERT_TRUE( 1 == radix_tree_get_ip_size(tree));

    locat = (location_t*)malloc(sizeof(location_t));
	locat->geo.country = 6;
	locat->geo.isp = 7;
	locat->geo.province = 8;
	locat->geo.region = 9;
	locat->geo.city = 10;
    CU_ASSERT_TRUE( 0 == radix_tree_insert_subnet(tree, "220.220.220.0/24", locat));
    CU_ASSERT_TRUE( 2 == radix_tree_get_ip_size(tree));

    locat = (location_t*)malloc(sizeof(location_t));
	locat->geo.country = 6;
	locat->geo.isp = 7;
	locat->geo.province = 8;
	locat->geo.region = 9;
	locat->geo.city = 10;
    CU_ASSERT_TRUE( 0 != radix_tree_insert_subnet(tree, "2.2.2./24", locat));
    CU_ASSERT_TRUE( 0 != radix_tree_insert_subnet(tree, "2.2.2./33", locat));
    CU_ASSERT_TRUE( 0 != radix_tree_insert_subnet(tree, "2.2.2.2", locat));
    CU_ASSERT_TRUE( 0 != radix_tree_insert_subnet(tree, "256.2.2.2", locat));
    CU_ASSERT_TRUE( 0 != radix_tree_insert_subnet(tree, "a.b.c.d/12", locat));
    CU_ASSERT_TRUE( 0 != radix_tree_insert_subnet(tree, "a.b.c.d", locat));


}

static void
test_trie_get_net(radix_tree_t *tree)
{
   location_t *locat;
   CU_ASSERT_TRUE( NULL != (locat = radix_tree_find_str(tree, "2.2.2.2") ));
   CU_ASSERT_TRUE( 1 == locat->geo.country);
   CU_ASSERT_TRUE( 2 == locat->geo.isp);
   CU_ASSERT_TRUE( 3 == locat->geo.province);
   CU_ASSERT_TRUE( 4 == locat->geo.region);
   CU_ASSERT_TRUE( 5 == locat->geo.city);
   CU_ASSERT_TRUE( NULL != (locat = radix_tree_find_str(tree, "2.2.2.122") ));
   CU_ASSERT_TRUE( NULL != (locat = radix_tree_find_str(tree, "220.220.220.2")) );
   CU_ASSERT_TRUE( 6 == locat->geo.country);
   CU_ASSERT_TRUE( 7 == locat->geo.isp);
   CU_ASSERT_TRUE( 8 == locat->geo.province);
   CU_ASSERT_TRUE( 9 == locat->geo.region);
   CU_ASSERT_TRUE( 10 == locat->geo.city);
   CU_ASSERT_TRUE( NULL != (locat = radix_tree_find_str(tree, "220.220.220.222")) );
   CU_ASSERT_TRUE( 6 == locat->geo.country);
   CU_ASSERT_TRUE( 7 == locat->geo.isp);
   CU_ASSERT_TRUE( 8 == locat->geo.province);
   CU_ASSERT_TRUE( 9 == locat->geo.region);
   CU_ASSERT_TRUE( 10 == locat->geo.city);
   CU_ASSERT_TRUE( NULL != (locat = radix_tree_find_str(tree, "220.220.220.255")) );
   CU_ASSERT_TRUE( 6 == locat->geo.country);
   CU_ASSERT_TRUE( 7 == locat->geo.isp);
   CU_ASSERT_TRUE( 8 == locat->geo.province);
   CU_ASSERT_TRUE( 9 == locat->geo.region);
   CU_ASSERT_TRUE( 10 == locat->geo.city);
   CU_ASSERT_TRUE( NULL == radix_tree_find_str(tree, "2.2.0.0") ); 
   CU_ASSERT_TRUE( NULL == radix_tree_find_str(tree, "xx2.2.0.0") ); 
   CU_ASSERT_TRUE( NULL == radix_tree_find_str(tree, "2.2.2.0/24") ); 

}

static void
test_trie_remove(radix_tree_t *tree)
{

    CU_ASSERT_TRUE( 0 == radix_tree_remove_subnet(tree, "2.2.2.0/24"));
    CU_ASSERT_TRUE( 1 == radix_tree_get_ip_size(tree));
    CU_ASSERT_TRUE( 0 != radix_tree_remove_subnet(tree, "2.2.2.0/24"));
    CU_ASSERT_TRUE( 1 == radix_tree_get_ip_size(tree));
    CU_ASSERT_TRUE( 0 != radix_tree_remove_subnet(tree, "2.2.22.0/24"));
    CU_ASSERT_TRUE( 0 != radix_tree_remove_subnet(tree, "a2..22.0/24"));
    CU_ASSERT_TRUE( 1 == radix_tree_get_ip_size(tree));
    CU_ASSERT_TRUE( 0 == radix_tree_remove_subnet(tree, "220.220.220.0/24"));
    CU_ASSERT_TRUE( 0 == radix_tree_get_ip_size(tree));
    CU_ASSERT_TRUE( 0 != radix_tree_remove_subnet(tree, "220.220.220.0/24"));
    CU_ASSERT_TRUE( 0 == radix_tree_get_ip_size(tree));
    CU_ASSERT_TRUE( 0 != radix_tree_remove_subnet(tree, "220.220.220.0/14"));
    CU_ASSERT_TRUE( 0 == radix_tree_get_ip_size(tree));
}

static void test_trie()
{
    radix_tree_t *tree ;
    tree = test_trie_create();
    test_trie_insert(tree);
    radix_tree_print(tree, traver_func);
    test_trie_get_net(tree);
    test_trie_remove(tree);
    
    const int random_num = 100000;

    p_twenty_array array_ip ;
    void * p = malloc(random_num * sizeof(char[20])) ; 
    if(NULL == p)
        printf("no p\n");

    array_ip = (p_twenty_array)p ;

    CU_ASSERT_TRUE(NULL != p);
    CU_ASSERT_TRUE(NULL != array_ip);
    if(NULL == array_ip)
    {
        printf("no malloc\n");
        return ;
    }
    
    random_insert(tree, array_ip, random_num);
    random_search(tree, array_ip, random_num);
    
    free(array_ip);

    test_trie_destroy(tree);
}

static void test_memory_pool()
{
    mem_pool_t *mp = mem_pool_create(sizeof(location_t), 100);
    CU_ASSERT_TRUE(NULL != mp);
    location_t *locat = NULL;
    locat = mem_pool_alloc(mp);
    CU_ASSERT_TRUE(NULL != mp);
    mem_pool_free(mp, locat);
    mem_pool_delete(mp);
}

static CU_TestInfo dump_tests[] = {
    {"test_trie", test_trie},
    {"test_memory_pool", test_memory_pool},
    CU_TEST_INFO_NULL,
};

static CU_SuiteInfo  first_suit[] = {
    {"trie_test", NULL, NULL, dump_tests},
    CU_SUITE_INFO_NULL,
};

int main()
{
    CU_BasicRunMode mode = CU_BRM_VERBOSE;
    CU_ErrorAction error_action = CUEA_FAIL;

    zrb_init_ruby_env();
    CU_initialize_registry();
    CU_register_suites(first_suit);
    CU_basic_set_mode(mode);
    CU_set_error_action(error_action);
    CU_basic_run_tests();
    CU_cleanup_registry();
    zrb_finalize_ruby_env();
    return 0;
}


